/* 
    This file is part of eJourn.

    eJourn is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    eJourn is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with eJourn; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */


#include "../dict.h"
#include "../xml.h"
#include "xmldict.h"
#include "../simple.h"





//heritage has the "." at the end.
//returns its offset, or NULL in case of serious error
//end is the tag to end on.
int _xml_to_dict(const char *xml, struct dict *d, 
						  const char *heritage, int offset, const char *end)
{
  char *parents;
  elog_sp_cat(&parents, heritage, NULL);

  int last = 0;
  /*
	0  -- nothing, [A-z] etc
	1  -- mode changed on last char, other than whitespace
  */
  int mode = 0;
  /*
	0  --  Start
	1  --  Inside open tag
	2  --  Inside close tag
	3  --  After open tag
	4  --  After close tag
  */
  int tag[2] = {0, 0}; //the beginning and end of our present tag

  int c=offset;
  while (xml[c]) {
	switch (xml[c]) {
	case '<': {
	  mode = 1;
	  last = 1;
	  tag[0] = c+1;
	  break;
	}
	case '>': {
	  last = 1;
	  tag[1] = c-1;
	  if (mode == 1) mode = 3;
	  else if (mode == 2) mode = 4;
	  break;
	}
	case '/': {
	  if (last && mode == 1) {
		mode = 2;
	  } else {
	  	elog_err_print("Error in xml, unescaped /\n");
	  	return -1;
	  }
	  tag[0] = c+1;
	  last = 1;
	  break;
	}
	case '\\': {
	  ++c; //++c again before loop comes back around
	  break;
	}
	case ' ': break;
	case '\t': break;
	case '\n': break;
	case '\r': break;
	default: 
	  last = 0;
	}
	if (mode == 3 && last) {
	  char word[tag[1] - tag[0] + 2];
	  
	  memset(word, '\0', tag[1] - tag[0] + 2);
	  int i=0;
	  for (; i <= tag[1] - tag[0]; ++i)
		word[i] = xml[tag[0] + i];

	  
	  for (i=2; xml[tag[1] + i]; ++i) {
		if (xml[tag[1]+i] == ' ' || 
			xml[tag[1]+i] == '\n' ||
			xml[tag[1]+i] == '\r' ||  
			xml[tag[1]+i] == '\t') {continue;}
		else if (xml[tag[1]+i] == '<') {
		  struct dict *t = dict_new();
		  char *tmp;
		  elog_sp_cat(&tmp, "<", word, ">.", NULL);

		  char *endTag;
		  elog_sp_cat(&endTag, "</", word, ">", NULL);

		  //c is pointing at ">" in an open tag...
		  char *endLoc = strstr(&(xml[c-(strlen(word)+1)]), endTag);
		  if (endTag < 0) {
		  	elog_err_print("No end tag for tag");
		  	elog_err_print(word);
		  	elog_err_print("\n");
		  	return c;
		  }
		  int endPos = (endLoc - &(xml[c-(strlen(word)+1)]))/(sizeof(char)) 
		  													 + strlen(word) - 3;
		  char ch = xml[c+endPos];

		  c += _xml_to_dict(&(xml[c+1]), t, "", 0, word);


		  free(endTag);
		  free(tmp);
		  if (!t) elog_err_print("Error in xml, no child dictionary!\n");
		  
		  /* Code to handle multiple tags of the same name */
		  if (dict_get(d, word) != NULL) {
		  	if (dict_get_type(d, word) == DICT_TYPE_DCT) {
		  		struct list *lst = list_new();
		  		list_add(lst, dict_get(d, word));
		  		list_type(lst, DICT_TYPE_DCT);
		  		list_free_able(lst, 1);
		  		
		  		list_add(lst, t);
		  		list_type(lst, DICT_TYPE_DCT);
		  		list_free_able(lst, 1);
		  		
		  		dict_set(d, word, lst);	
		  		dict_set_type(d, word, DICT_TYPE_LST);
		  		dict_set_free(d, word, 1);
		  	}
		  	else if (dict_get_type(d, word) == DICT_TYPE_LST) {
		  		struct list *lst = dict_get(d, word);
		  		list_add(lst, t);
		  		list_type(lst, DICT_TYPE_DCT);
		  		list_free_able(lst, 1);
		  	}
		  }
		  /* end code for multiple tags of one name */
		  else {
			  dict_set(d, word, t);
			  dict_set_type(d, word, DICT_TYPE_DCT);
			  dict_set_free(d, word, 1);
		  }
		  mode = 4; //we're now just after a close tag
		  
		  break;
		}
		else {
		  //it's an item..
		  char *content = NULL;
		  char *templ;
		  elog_sp_cat(&templ, parents, "<", word, ">.%s", NULL);
		  elog_xml_scanf(xml, templ, &content);
		  if (!content) printf("Error in xml!\n");

		  free(templ);
		  dict_set(d, word, content);
		  break;
		}
	  }
	}
	if (mode == 4 && last) {
	  char word[tag[1] - tag[0] + 2];
	  
	  memset(word, '\0', tag[1] - tag[0] + 2);
	  int i=0;
	  for (; i <= tag[1] - tag[0]; ++i)
		word[i] = xml[tag[0] + i];
	  if (strcmp(word, end) == 0) {
		free(parents);
		return (tag[1] - offset) ;
	  }
	}
	++c;
  }
  free(parents);
  return c - offset;
}

struct dict *elog_xml_to_dict(const char *xml)
{
  struct dict *d = dict_new();
  _xml_to_dict(xml, d, "", 0, "");
  if (!(d->root)) {
	dict_free(d);
	return NULL;
  }
  struct dict *ret = d->root->value;
  d->root = NULL;
  dict_free(d);
  return ret;
}

#define CAT(x, y) xmldct__cat(x, y)
char *xmldct__cat(char *dest, const char *x)
{
	while (*dest)
		dest++;
	while ((*dest++ = *x++));	//apparently this works?
	return --dest;
}

char *elog_xml_endTag(const char *tag);

/*
  Optimized for speed, not memory efficiency.
  Internal recursions are held, so RAM needed should be doubled, or more.
  elog_xml_printf and scanf routines are used at a small speed cost.
*/
char *elog_dict_to_xml(struct dict *dct, const char *meta)
{
  char *xml = NULL; //elog_xml_start(meta, 512*1024);

  int xmls [dct->size];
  memset(xmls, 0, dct->size);
  char *recurses [dct->size];
  memset(recurses, 0, dct->size);

  /*
	First figure out the space we need
	and do any necessary recursions, we'll need them for space.
  */
  struct dict_value *p = dct->root;
  int c=0;
  while (p) {
	switch (p->type) {
	case DICT_TYPE_INT: {
	  xmls[c] = strlen(p->name)*2 + 30;
	  break;
	}
	case DICT_TYPE_STR: {
	  char *val = p->value;
	  xmls[c] = (strlen(p->name) + 3)*2 + strlen(val) + 5;
	  break;
	}
	case DICT_TYPE_DCT: {
	  struct dict *d = p->value;
	  char *temp;
	  elog_sp_cat(&temp, "<", p->name, ">", NULL);
	  recurses[c] = elog_dict_to_xml(d, temp);
	  free(temp);
	  xmls[c] = strlen(recurses[c]) + 1;
	  break;
	}
	case DICT_TYPE_OBJ: {

	  break;
	}
	case DICT_TYPE_LST: {
		struct list_item *item = ((struct list *)p->value)->items;
		int size=0;
		char *subtexts [list_size(p->value)];
		int l=0;
		while (item) {
			if (item->type == DICT_TYPE_DCT) {
				char *name;
				elog_sp_cat(&name, "<", p->name, ">", NULL);
				subtexts[l] = elog_dict_to_xml(item->value, name);
				free(name);
				size += strlen(subtexts[l]);	
			} else {
				subtexts[l] = NULL;
				elog_err_print("Non-dict type item in list for transfer to xml.");	
			}
			++l;
			item=item->next;
		}
		char *text = malloc(size+1);
		text[0] = '\0';
		int i;
		char *tmp = text;
		for (i=0; i < l; ++i)
			if (subtexts[i]) 
				tmp = CAT(tmp, subtexts[i]);
		
		recurses[c] = text;
		xmls[c] = size+1;
		break;	
	}
	default:
	  xmls[c] = 0;
	}
	++c;
	p = p->next;
  }
  int space = 2 + strlen(meta)*2 + 4;
  for (c=0; c < dct->size; ++c) {
	if (xmls[c])
	  space = space + xmls[c];
  }


  /*
	Now build the xml.
  */
  xml = elog_xml_start(meta, space + dct->size * 4);
  char *template;
  elog_sp_cat(&template, meta, ".%s", NULL);
  p = dct->root;
  c=0;
  while (p) {
	if (xmls[c] <= 0) {
	  p=p->next;
	  ++c;
	  continue;
	}
	char *tag_name = NULL;
	elog_sp_cat(&tag_name, "<", p->name, ">", NULL);

	switch (p->type) {
	case DICT_TYPE_INT: {
	  int *i_i = p->value;
	  char *i = elog_sp_shortToString(*i_i);
	  elog_xml_printf(xml, template, tag_name, i);
	  free(i);
	  break;
	}
	case DICT_TYPE_STR: {
	  char *val = p->value;
	  elog_xml_printf(xml, template, tag_name, val);

	  break;
	}
	case DICT_TYPE_DCT: {
	  if (recurses[c]) {
		int name_size = strlen(p->name)+2;
		char *val = &(recurses[c][name_size]);
		recurses[c][xmls[c]-(name_size+2)] = '\0';

		char *my_temp;
		elog_sp_cat(&my_temp, meta, ".%x", NULL);
				
		elog_xml_printf(xml, my_temp, tag_name, val);
		free(my_temp);
		free(recurses[c]);
	  }
	  break;
	}
	case DICT_TYPE_LST: {
		if (recurses[c]) {
			char *my_temp;
			elog_sp_cat(&my_temp, meta, ".%x", NULL);

			strncpy(&(xml[strlen(meta)]), recurses[c], strlen(recurses[c]));
			char *endTag = elog_xml_endTag(meta);
			CAT(xml, endTag);
			free(endTag);
			/* WARNING:
			 * 	This code assumes meta will always be a single tag.
			 * 
			 */

			free(my_temp);
			free(recurses[c]);
		}
		break;	
	}
	case DICT_TYPE_OBJ: {
	  
	  break;
	}
	}
	free(tag_name);
	p=p->next;
	++c;
  }
  free(template);
  
  
  return xml;
}

#ifdef TEST

struct dict *make_dict()
{
	struct dict *d = dict_new();
	dict_set(d, "bob", CPY_STR("is fascinating"));
	dict_set(d, "tracy", CPY_STR("isn't fascinating"));
	
	struct dict *d2 = dict_new();
	dict_set(d2, "bob likes what", CPY_STR("I \\like donkeys"));
	  
	dict_set(d, "about bob", d2);
	dict_set_type(d, "about bob", DICT_TYPE_DCT);
	
	
	struct dict *d3 = dict_new();
	dict_set(d3, "and wha?", CPY_STR("y>es"));
	  
	dict_set(d2, "query", d3);
	dict_set_type(d2, "query", DICT_TYPE_DCT);
	
	int *n = HEAP_INT(5);
	dict_set(d3, "a number", n);
	dict_set(d2, "a number", CPY_STR("3"));
	dict_set(d, "a number", CPY_STR("7"));
	dict_set_type(d3, "a number", DICT_TYPE_INT);
	return d;
}

int xml_l_call(void *dat, char *xml, const char *tag)
{
	char *mysig;
	elog_sp_cat(&mysig, tag, ".%s", NULL);
	return elog_xml_printf(xml, mysig, "<first name>", (char *)dat);
}

char *xml_make()
{
	char *xml = elog_xml_start("<Junk>", 1024);
	elog_xml_printf(xml, "<Junk>.%s", "<hi>", "hello world");

	char *data [] = {	"bob", "sally", "susan", NULL	};
	elog_xml_printl(xml, "<Junk>", "<names>", "<name>", xml_l_call,
							(void *)data, 100);
	
	
	
	return xml;
}

struct dict *_xml_1t_3to_dict(struct elog_xml_doc *doc);

int main(void)
{
/*	char *xml = xml_make();
	//char *pretty = elog_xml_readify(xml, -1);
	//printf("%s\n", pretty);

	struct dict *d = elog_xml_to_dict(xml);
	//dict_print(d);
		
	free(xml);

	xml = elog_dict_to_xml(d, "<Junk>");
	char *pretty = elog_xml_readify(xml, -1);
	printf("%s\n", pretty);
	free(pretty);
	free(xml);

	
	dict_set(d, "object", CPY_STR("some data..."));
	dict_set_type(d, "object", DICT_TYPE_OBJ);
	dict_set_free(d, "object", 1);*/

	struct elog_xml_doc *doc = elog_xml_open("ver3.dry", 0);
	printf("Subject:%s\n", doc->subject);

	struct dict *file = _xml_1t_3to_dict(doc);
	dict_print(file);

	elog_io_close(dict_get(file, "handle"));

	dict_unref(file);

	return 0;
}

#endif

